//=========================================================================
//  CSOCKETRTSCHEDULER.CC - part of
//
//                  OMNeT++/OMNEST
//           Discrete System Simulation in C++
//
// Author: Andras Varga, 2005
//
//=========================================================================

/*--------------------------------------------------------------*
  Copyright (C) 2005-2008 Andras Varga

  This file is distributed WITHOUT ANY WARRANTY. See the file
  `license' for details on this and other legal matters.
*--------------------------------------------------------------*/


#include "cSocketRTScheduler.h"
#include "ExtAndroidClient.h"
#include <platdep/sockets.h>
#include <ext/hash_map>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

Register_Class(cSocketRTScheduler);

Register_GlobalConfigOption(CFGID_SOCKETRTSCHEDULER_PORT, "socketrtscheduler-port", CFG_INT, "4242", "When cSocketRTScheduler is selected as scheduler class: the port number cSocketRTScheduler listens on.");

inline std::ostream& operator<<(std::ostream& out, const timeval& tv)
{
    return out << (unsigned long)tv.tv_sec << "s" << tv.tv_usec << "us";
}

//---

cSocketRTScheduler::cSocketRTScheduler() : cScheduler()
{

    listenerSocket = INVALID_SOCKET;
    dummySocket = INVALID_SOCKET;

    connected_clients = 0;
}

cSocketRTScheduler::~cSocketRTScheduler()
{
}

void cSocketRTScheduler::startRun()
{
    if (initsocketlibonce()!=0)
        throw cRuntimeError("cSocketRTScheduler: Cannot initialize socket library");

    gettimeofday(&baseTime, NULL);

    recvBuffer = NULL;
    recvBufferSize = 0;
    numBytesPtr = NULL;

    port = ev.getConfig()->getAsInt(CFGID_SOCKETRTSCHEDULER_PORT);
    setupListener();
}

void cSocketRTScheduler::setupListener()
{
    //Init listener socket
    listenerSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (listenerSocket==INVALID_SOCKET)
        throw cRuntimeError("cSocketRTScheduler: cannot create socket");

    /**///Set listener to nonblocking
    int x;
    x=fcntl(listenerSocket,F_GETFL,0);
    fcntl(listenerSocket,F_SETFL,x | O_NONBLOCK);
    /**/

    //Set address and bind
    sockaddr_in sinInterface;
    sinInterface.sin_family = AF_INET;
    sinInterface.sin_addr.s_addr = INADDR_ANY;
    sinInterface.sin_port = htons(port);
    if (bind(listenerSocket, (sockaddr*)&sinInterface, sizeof(sockaddr_in))==SOCKET_ERROR)
        throw cRuntimeError("cSocketRTScheduler: socket bind() failed");

    //Begin listening
    listen(listenerSocket, SOMAXCONN);

    max_FD=listenerSocket;

    logMessage("Listener started!");
}

void cSocketRTScheduler::endRun()
{
    //TODO:Close all sockets
    closesocket(listenerSocket);
    closesocket(dummySocket);

    //Close sockets for each module
    for (int i=0; i < client_modules.size(); i++){
        ExtAndroidClient* andClient = dynamic_cast<ExtAndroidClient *>(client_modules[i]);
        andClient->closeSocket();
    }
}

void cSocketRTScheduler::executionResumed()
{
    gettimeofday(&baseTime, NULL);
    baseTime = timeval_substract(baseTime, SIMTIME_DBL(simTime()));
}

//New Multi-external client version
void cSocketRTScheduler::setInterfaceModule(cModule *mod, cMessage *notifMsg)
{
    //@author[Ken]:Create the wrapper and add it to modules list
    EV << "Called by:" + mod->getFullPath();
    client_modules.push_back(mod);

}

void cSocketRTScheduler::InitSets()
{
    logMessage("Initializing FDs...");
    //Initialize
    FD_ZERO(&readFDs);
    FD_ZERO(&writeFDs);
    FD_ZERO(&exceptFDs);

    //Assign the listenerSocket to Sets
    if(listenerSocket==INVALID_SOCKET){
        throw cRuntimeError("listenerSocket is Invalid!");
        return;
    }
    FD_SET(listenerSocket, &readFDs);
    FD_SET(listenerSocket, &exceptFDs);

    //
    for(int i=0; i<this->client_modules.size(); i++){
        ExtAndroidClient* andClient = dynamic_cast<ExtAndroidClient *>(client_modules[i]);
        /*///TODO
        if(andClient->getSentBytes() < andClient->getTotalBytes())
        {
           //We have data to send
           FD_SET(andClient->getSocket(), &writeFDs);
        }
        else
        {
           //We can read on this socket
           FD_SET(andClient->getSocket(), &readFDs);
        }
        /**/
        //Add it to Read Set
        FD_SET(andClient->getSocket(), &readFDs);
        //Add it to Exception Set
        FD_SET(andClient->getSocket(), &exceptFDs);
        EV<< "cSocketRTScheduler: " << (i+1) << "/" << this->client_modules.size() << "\n";
    }
    logMessage("FDs initialized!");
}

cModule* cSocketRTScheduler::getFreeAndroidClientModule(){
    for(int i=0; i < this->client_modules.size(); i++){
        ExtAndroidClient* andClient = dynamic_cast<ExtAndroidClient *>(client_modules[i]);
        if(andClient->getSocket() == INVALID_SOCKET)
            return andClient;
    }
    return NULL;
}

//When using select() multiple sockets may have errors
//This function will give us the socket specific error
//WSAGetLastError() can't be relied upon
int GetSocketSpecificError(SOCKET Socket)
{
     int nOptionValue;
     int nOptionValueLength = sizeof(nOptionValue);

     //Get error code specific to this socket
     getsockopt(Socket, SOL_SOCKET, SO_ERROR, (char*)&nOptionValue,(socklen_t*)&nOptionValueLength);

     return nOptionValue;
}

bool cSocketRTScheduler::receiveWithTimeout(long usec)
{
    //logMessage("Call to receiveWithTimeout...");
    InitSets();

    timeval timeout;
    timeout.tv_sec = 1;
    timeout.tv_usec = usec;

    EV << "FD_SETSIZE = " << FD_SETSIZE <<"\n";

    if (select(max_FD+1, &readFDs, &writeFDs, &exceptFDs, &timeout) > 0)
    {
        logMessage("cSocketRTScheduler: Socket state has changed!");
        //One of the socket changed state, let's process it.

        //ListenSocket?  Accept the new connection
        if (FD_ISSET(listenerSocket, &readFDs))
        {
            logMessage("Handling new connection...");
            sockaddr_in ClientAddress;
            int nClientLength = sizeof(ClientAddress);

            //Accept remote connection attempt from the client
            SOCKET Socket = accept(listenerSocket, (sockaddr*)&ClientAddress, (socklen_t*)&nClientLength);
            if (INVALID_SOCKET == Socket)
            {
                //EV <<"\nError occurred while accepting socket: ." << GetSocketSpecificError(listenerSocket);
                //char* err_msg = "cSocketRTScheduler: Error occurred while accepting socket:" << GetSocketSpecificError(listenerSocket);
                //throw cRuntimeError(err_msg);
                throw cRuntimeError("cSocketRTScheduler: Socket accept() failed!");
            }

            //Making it a non blocking socket
            int x;
            x=fcntl(Socket,F_GETFL,0);
            fcntl(Socket,F_SETFL,x | O_NONBLOCK);

            //Assign socket to an available ExtAndroidClient module
            cModule* cmod = getFreeAndroidClientModule();
            if(cmod==NULL)
                throw cRuntimeError("cSocketRTScheduler: WHOOPS! Client tried to connect, but no available module was found.");
            ExtAndroidClient* andClient = dynamic_cast<ExtAndroidClient *>(cmod);
            if(andClient!=NULL){
                andClient->setSocket(Socket);

                if(max_FD <= Socket)
                    max_FD=Socket;

                EV << "cSocketRTScheduler: Client connected from [" << inet_ntoa(ClientAddress.sin_addr) << "]\n";
            }else{
                closesocket(Socket);
                Socket=INVALID_SOCKET;
                throw cRuntimeError("cSocketRTScheduler: Client tried to connect, but no available module was found.");
            }
        }
        //Error occurred for ListenSocket?
        if (FD_ISSET(listenerSocket, &exceptFDs))
        {
            //char* err_msg ="cSocketRTScheduler: Error occurred on listener socket: " << GetSocketSpecificError(listenerSocket);
            //throw cRuntimeError(err_msg);
            throw cRuntimeError("cSocketRTScheduler: Error occurred on listener socket.");
        }

        for(int i=0; i<this->client_modules.size(); i++){
            ExtAndroidClient* andClient = dynamic_cast<ExtAndroidClient *>(client_modules[i]);

            //Check in Read Set
            if (FD_ISSET(andClient->getSocket(), &readFDs))
            {
                //TODO:HERE
                logMessage("cSocketRTScheduler: Handling message from client...");
                int nBytes = recv(andClient->getSocket(), andClient->getBuffer(), 4000, 0);
                if ((0 == nBytes) || (SOCKET_ERROR == nBytes))
                {
                    //printf("\nError occurred while receiving on the socket: %d.", GetSocketSpecificError(andClient->getSocket()));
                    EV << "cSocketRTScheduler: socket closed by the client\n";
                    if (shutdown(andClient->getSocket(), SHUT_WR) == SOCKET_ERROR)
                        throw cRuntimeError("cSocketRTScheduler: shutdown() failed!");
                    andClient->closeSocket();

                    //continue;
                    return false;
                }

                EV << "cSocketRTScheduler: received " << nBytes << " bytes\n";

                //Set the buffer
                andClient->setTotalBytes(nBytes);
                andClient->setSentBytes(0);

                //printf("\nThe following message was received: %s", pClientContext->GetBuffer());
                //@NOTE
                // Do nothing with the buffer, send message to module that there's something in the buffer
                // Module handles the buffer's contents within itself

                //Compute current time
                timeval curTime;
                gettimeofday(&curTime, NULL);
                curTime = timeval_substract(curTime, baseTime);
                simtime_t t = curTime.tv_sec + curTime.tv_usec*1e-6;

                // TBD assert that it's somehow not smaller than previous event's time
                // Send message to simulator
                cMessage* notificationMsg = andClient->getRtEvent();
                notificationMsg->setArrival(andClient,-1,t);
                simulation.msgQueue.insert(notificationMsg);
                return true;
            }

            //Check in Exception Set
            if (FD_ISSET(andClient->getSocket(), &exceptFDs))
            {
                //TODO:
                throw cRuntimeError("cSocketRTScheduler: Error occurred on client socket!");
            }
        }
    }

    return false;
}

int cSocketRTScheduler::receiveUntil(const timeval& targetTime)
{
    // if there's more than 200ms to wait, wait in 100ms chunks
    // in order to keep UI responsiveness by invoking ev.idle()
    timeval curTime;
    gettimeofday(&curTime, NULL);
    while (targetTime.tv_sec-curTime.tv_sec >=2 ||
           timeval_diff_usec(targetTime, curTime) >= 200000)
    {
        if (receiveWithTimeout(100000)) // 100ms
            return 1;
        if (ev.idle())
            return -1;
        gettimeofday(&curTime, NULL);
    }

    // difference is now at most 100ms, do it at once
    long usec = timeval_diff_usec(targetTime, curTime);
    if (usec>0)
        if (receiveWithTimeout(usec))
            return 1;
    return 0;
}

cMessage *cSocketRTScheduler::getNextEvent()
{
    /*/ assert that we've been configured
    if (!module)
        throw cRuntimeError("cSocketRTScheduler: setInterfaceModule() not called: it must be called from a module's initialize() function");
    /**/

    // calculate target time
    timeval targetTime;
    cMessage *msg = sim->msgQueue.peekFirst();
    if (!msg)
    {
        // if there are no events, wait until something comes from outside
        // TBD: obey simtimelimit, cpu-time-limit
        targetTime.tv_sec = LONG_MAX;
        targetTime.tv_usec = 0;
    }
    else
    {
        // use time of next event
        simtime_t eventSimtime = msg->getArrivalTime();
        targetTime = timeval_add(baseTime, SIMTIME_DBL(eventSimtime));
    }

    // if needed, wait until that time arrives
    timeval curTime;
    gettimeofday(&curTime, NULL);
    if (timeval_greater(targetTime, curTime))
    {
        int status = receiveUntil(targetTime);
        if (status == -1)
            return NULL; // interrupted by user
        if (status == 1)
            msg = sim->msgQueue.peekFirst(); // received something
    }
    else
    {
        // we're behind -- customized versions of this class may
        // alert if we're too much behind, whatever that means
    }

    // ok, return the message
    return msg;
}

void cSocketRTScheduler::sendBytes(SOCKET connSocket, const char *buf, size_t numBytes)
{
    if (connSocket==INVALID_SOCKET)
        throw cRuntimeError("cSocketRTScheduler: sendBytes(): no connection");

    send(connSocket, buf, numBytes, 0);
    // TBD check for errors
}

void cSocketRTScheduler::logMessage(const char* msg){
    EV << "cSocketRTScheduler: " << msg << "\n";

}
